import base64
import hmac
import secrets
import sys
import time
import datetime
from enum import Enum
import re
import os

ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
printable = digits + ascii_letters + punctuation


class server_response(Enum):
    re_220 = "220 Service ready"
    re_221 = "221 Service closing transmission channel"
    re_235 = "235 Authentication successful"
    re_250 = "250 Requested mail action okay completed"
    re_334 = "334"
    re_354 = "354 Start mail input end <CRLF>.<CRLF>"
    re_421 = "421 Service not available, closing transmission channel"
    re_500 = "500 Syntax error, command unrecognized"
    re_501 = "501 Syntax error in parameters or arguments"
    re_503 = "503 Bad sequence of commands"
    re_504 = "504 Unrecognized authentication type"
    re_535 = "535 Authentication credentials invalid"
    re_err_conn = "Cannot establish connection"
    re_err_code = [421, 500, 501, 503, 504, 535]


class commands(Enum):
    EHLO = "EHLO"
    MAIL = "MAIL"
    RCPT = "RCPT"
    DATA = "DATA"
    AUTH = "AUTH"
    QUIT = "QUIT"
    REST = "RSET"
    DATA_BODY = "DATA_BODY"
    DATA_END = "DATA_END"
    AUTH_CHALLENGE = "AUTH_CHALLENGE"
    NOOP = "NOOP"


def parse_response(res: str):
    res = res.rstrip('\r\n')
    res = re.search('(\d{3})(.*)', res)
    if res is None:
        return None, None
    return res.groups()[0].rstrip(' ').rstrip('\r\n').lstrip(' '), res.groups()[1].rstrip(' ').rstrip('\r\n').lstrip(
        ' ')


valid_mail = "([a-zA-Z0-9]+([a-zA-Z0-9]+|-)*)(\.[a-zA-Z0-9]+([a-zA-Z0-9]+|-)*)*" \
             "@" \
             "(([a-zA-Z0-9]+)(([a-zA-Z0-9]+|-)*[a-zA-Z0-9]+)?(\.([a-zA-Z0-9]+)(([a-zA-Z0-9]+|-)*[a-zA-Z0-9]+)?)+)" \
             "|" \
             "\[((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.){4}\]"


def is_cmd_valid(cmd, param, res: str):
    res = res.rstrip('\r\n')
    _ = [cmd, param]
    if cmd == commands.EHLO.value:
        return bool(re.match(r'^(EHLO) (((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4})$', res))
    elif cmd == commands.MAIL.value:
        # TODO :// email validation
        return bool(re.match(rf'^(MAIL FROM:)(<({valid_mail})>)', res))
    elif cmd == commands.RCPT.value:
        return bool(re.match(rf'^(RCPT TO:)(<({valid_mail})>)', res))
    elif cmd == commands.DATA.value:
        return bool(re.match(r'^DATA$', res))
    elif cmd == commands.REST.value:
        return bool(re.match(r'^RSET$', res))
    elif cmd == commands.QUIT.value:
        return bool(re.match(r'^QUIT$', res))
    elif cmd == commands.NOOP.value:
        return bool(re.match(r'^NOOP$', res))
    elif cmd == commands.AUTH.value:
        if res != ' '.join(_): return False
    elif cmd in [commands.AUTH_CHALLENGE.value, commands.DATA_BODY.value, commands.DATA_END.value]:
        return True
    return True


def parse_cmd(msg: str, pre_state=None):
    msg = msg.rstrip('\r\n')
    msg = msg.lstrip(' ').rstrip(' ')
    res = [None, None, None]
    if msg == '.':
        res[0] = commands.DATA_END.value
        res[2] = '.'
        res[1] = msg
        return res
    else:
        for i in commands:
            if msg.startswith(i.value):
                if pre_state != commands.DATA_BODY.value and pre_state != commands.DATA.value:
                    res[0] = i.value
                    res[1] = msg[len(i.value):].rstrip(' ').lstrip(' ') if msg[len(i.value):].rstrip(' ').lstrip(
                        ' ') != "" else msg
                    res[2] = msg
                    return res
                else:
                    res[0] = commands.DATA_BODY.value
                    res[1] = res[2] = msg
                    return res
        if pre_state == commands.AUTH.value:
            res[0] = commands.AUTH_CHALLENGE.value
            res[1] = res[2] = msg
            return res
        res[0] = commands.DATA_BODY.value
        res[1] = msg
        res[2] = msg
        return res


def send_msg(conn, msg):
    msg = (msg + '\r\n').encode()
    conn.sendall(msg)


def print_to_client(msg,multi=False,pid=None,order=None):
    msg = msg.rstrip('\r\n')
    if not multi:
        print(f"C: {msg}\r\n", end="", file=sys.stdout, flush=True)
    else:
        print(f"[{pid}][{str(order).zfill(2)}]C: {msg}\r\n",end="",file=sys.stdout,flush=True)


def print_to_fake_server(msg):
    msg = msg.lstrip(' ').rstrip('\r\n')
    if msg == "250 127.0.0.1\r\n250 AUTH CRAM-MD5":
        print("AC: 250 127.0.0.1\r\n", end="", file=sys.stdout, flush=True)
        print("AC: 250 AUTH CRAM-MD5\r\n", end="", file=sys.stdout, flush=True)
        return
    print(f"AC: {msg}\r\n", end="", file=sys.stdout, flush=True)


def print_to_fake_client(msg):
    msg = msg.rstrip('\r\n')
    print(f"AS: {msg}\r\n", end="", file=sys.stdout, flush=True)


def print_to_server(msg,multi=False,pid=None,order=None):
    if not multi:
        msg = msg.lstrip(' ')
        msg = msg.rstrip('\r\n')
        if msg == "250 127.0.0.1\r\n250 AUTH CRAM-MD5":
            print("S: 250 127.0.0.1\r\n", end="", file=sys.stdout, flush=True)
            print("S: 250 AUTH CRAM-MD5\r\n", end="", file=sys.stdout, flush=True)
            return
        print(f"S: {msg}\r\n", end="", file=sys.stdout, flush=True)
    else:
        msg = msg.lstrip(' ').rstrip('\r\n')
        if msg == "250 127.0.0.1\r\n250 AUTH CRAM-MD5":
            print(f"[{pid}]{order}S: 250 127.0.0.1\r\n",end="",file=sys.stdout,flush=True)
            print(f"[{pid}]{order}S: 250 AUTH CRAM-MD5\r\n",end="",file=sys.stdout,flush=True)
            return
        print(f"[{pid}][{str(order).zfill(2)}]S: {msg}\r\n",end="",file=sys.stdout,flush=True)


def load_config(path):
    try:
        with open(path, 'r') as f:
            lines = f.readlines()
        config = {'DEFAULT': {}}
        for line in lines:
            k, v = [i.lstrip(' ').rstrip(' ').replace('\n', '') for i in line.split('=')]
            config['DEFAULT'][k] = v
        return config
    except Exception as e:
        exit(2)


def read_all_mail(path):
    mail_queue = []
    files = sorted(os.listdir(path))
    auth = False
    for file in files:
        if file.startswith('auth.'):
            auth = True
        with open(os.path.join(path, file), 'r', encoding='ascii') as f:
            content = f.read().split('\n')
            mail = parse_mail_transaction(content)
            f = os.path.abspath(path) + "/" + file
            mail_queue.append((list(mail) + [f]))
    return mail_queue, auth


def parse_mail_transaction(mails):
    content = []
    sender = receiver = subject = date = ""
    bad_format = False
    for line in mails:
        if line.startswith('From:'):
            if re.search('From:(.*)', line) is not None:
                sender = re.search('From:(.*)', line).groups()[0].lstrip(' ')
            # sender = line.split(':')[1]
        elif line.startswith('To:'):
            if re.search('To:(.*)', line) is not None:
                receiver = re.search('To:(.*)', line).groups()[0].lstrip(' ')
        elif line.startswith('Subject:'):
            if re.search('Subject:(.*)', line) is not None:
                subject = re.search('Subject:(.*)', line).groups()[0].lstrip(' ').lstrip(':').lstrip(' ')
        elif line.startswith('Date:'):
            if re.search('Date:(.*)', line) is not None:
                date = re.search('Date:(.*)', line).groups()[0].lstrip(' ').lstrip(':').lstrip(' ')
        else:
            if line != "":
                content.append(line)
    if sender == "" or receiver == "" or subject == "" or date == "":
        bad_format = True
    if receiver != "":
        receiver = receiver.split(',')
    else:
        receiver = []
    return sender, receiver, date, subject, content, bad_format


def send_mail(data: dict, already_sent: dict, client):
    sent_order = ['sender', 'receiver', 'date', 'subject', 'content']
    s = [already_sent[i] for i in sent_order]
    if already_sent['receiver'] == 0:
        send_msg(client, f"{commands.RCPT.value} TO:{data['receiver'][0].lstrip(' ').rstrip(' ')}")
        print_to_client(f"{commands.RCPT.value} TO:{data['receiver'][0].lstrip(' ').rstrip(' ')}")
        del data['receiver'][0]
        if len(data['receiver']) == 0:
            already_sent['receiver'] = 1
    elif already_sent['date'] == 0:
        send_msg(client, f"Date: {data['date']}")
        print_to_client(f"Date: {data['date']}")
        already_sent['date'] = 1
    elif already_sent['subject'] == 0:
        send_msg(client, f"Subject: {data['subject']}")
        print_to_client(f"Subject: {data['subject']}")
        already_sent['subject'] = 1
    elif already_sent['content'] == 0:
        send_msg(client, f"{data['content'][0]}")
        print_to_client(f"{data['content'][0]}")
        del data['content'][0]
        if len(data['content']) == 0:
            already_sent['content'] = 1
    return already_sent


def save_file(path, content, name,mutil=False,pid=None,order=None):
    if not os.path.exists(path):
        os.makedirs(path)
    if not mutil:
        with open(f"{path}/{name}.txt", 'w') as f:
            for k, v in content.items():
                if k != "Body":
                    f.write(f"{k}: {v}\n")
                else:
                    f.write(f"{v}")
    else:
        with open(f"{path}/[{pid}][{order}]{name}.txt", 'w') as f:
            for k, v in content.items():
                if k != "Body":
                    f.write(f"{k}: {v}\n")
                else:
                    f.write(f"{v}")


def save_mail(path, mail_buffer,multi=False,pid=None,order=None):
    dst = []
    body = []
    src = date = subject = ""
    for k, v in mail_buffer:
        if k == commands.MAIL.value and v.startswith("FROM:"):
            src = re.search("FROM:(.*)", v).groups()[0].lstrip(' ')
        elif k == commands.RCPT.value and v.startswith("TO:"):
            dst.append(re.search("TO:(.*)", v).groups()[0].lstrip(' '))
        elif k == commands.DATA_BODY.value and v.startswith("Date"):
            date = re.search("Date(.*)", v).groups()[0].lstrip(' ').lstrip(':').lstrip(' ')
        elif k == commands.DATA_BODY.value and v.startswith("Subject"):
            subject = re.search("Subject(.*)", v).groups()[0].lstrip(' ').lstrip(':').lstrip(' ')
        else:
            body.append(v)
    if len(body):
        bucket = {'From': src, 'To': ','.join(dst), 'Date': date, 'Subject': subject, 'Body': '\n'.join(body) + '\n'}
    else:
        bucket = {'From': src, 'To': ','.join(dst), 'Date': date, 'Subject': subject}
    save_file(path, bucket, get_unix_timestamp(date),multi,pid,order)


def get_unix_timestamp(date):
    return str(int(time.mktime(datetime.datetime.strptime(date, "%a, %d %b %Y %H:%M:%S %z").timetuple())))


def generate_nonce(n=32):
    # TODO：// 修改回下面的表达式
    seq = [secrets.choice(ascii_letters) for i in range(n)]
    return base64.b64encode(''.join(seq).encode('ascii'))


def generate_hmac_value(secret, token):
    return hmac.new(secret.encode(), base64.b64decode(token), digestmod='md5').hexdigest()


if __name__ == '__main__':
    l = [('MAIL', 'FROM:<bob@bob.org>'),
         ('RCPT', 'TO: <alice@example.com>'),
         ('RCPT', 'TO: <me@carol.me>'),
         ('DATA_BODY', 'Date: Mon, 14 Sep 1987 23:07:00 +1000'),
         ('DATA_BODY', 'Subject: Frist Electronic Mail from Bob to Alice and Carol'),
         ('DATA_BODY', 'Across the Exosphere we can reach every corner on the Moon.'),
         ('DATA_BODY', 'P.S. THIS IS NOT A SPAM')]

    save_mail('./send', l)
